/***************************************************************
* m3d_spl.h
*
* M3D splines
***************************************************************/
#pragma once

enum {
   M3D_SPL_NONE = -1,
   M3D_SPL_LINEAR1D,  
   M3D_SPL_LINEAR2D,  
   M3D_SPL_LINEAR3D,  
   M3D_SPL_HERMIT,  
   M3D_SPL_BEZIER2D,  
   M3D_SPL_BEZIER3D,  
   M3D_SPL_LAGRANGE,
   M3D_SPL_QUAT,
   M3D_SPL_COLOR,

   M3D_SPL_UNKNOWN = 1000, 
};

const DWORD M3D_SPL_MAX_KP_DIM            = 20;

const DWORD M3D_SPL_ST_HALFRATE           = 0x01;
const DWORD M3D_SPL_ST_CHUNKED            = 0x02;        // spline in raw / chunked format
const DWORD    M3D_SPL_RAW_ST_NO_ARG      = 0x04;        // arg == (float)kp and arg field is removed

//
// Base spline definition
//
struct m3dSPL_KP {
   float    arg;        // keypoint argument
};

const DWORD M3D_SPL_OPT_ST_CHUNKS            = 1 << 0;
const DWORD M3D_SPL_OPT_ST_HALFRATE          = 1 << 1;

const DWORD M3D_SPL_OPT_ST_RAW_REMOVE_ARG    = 1 << 2;
const DWORD M3D_SPL_OPT_ST_RAW_MAKE_INT_ARG  = 1 << 3;

const DWORD M3D_SPL_OPT_ST_CHUNK_EVEN_SIZE   = 1 << 4;
const DWORD M3D_SPL_OPT_ST_CHUNK_NO_MERGE    = 1 << 5;

struct m3dSPL_OPTIMIZE {
   apSTATE        state;
   int            minChunk, maxChunk;
   float          maxErrorConst;
   float          maxErrorPack;
   float          maxHalfRateError;

   m3dSPL_OPTIMIZE (
      int   _state            = 0, 
      float _maxErrorConst    = 0.004f, 
      float _maxErrorPack     = 0.003f, 
      float _maxHalfRateError = 0.005f);
};

struct m3dSPL_INFO {
   int            memUsed;
   int            numChunks;
   int            numChunksWoDim;
   int            maxChunkSize;
   int            minChunkSize;
   int            sumChunkSize;
   int            numConstTracks;
   int            numByteChunks;
   int            numWordChunks;
   int            numConstChunks;
   int            numMergedChunks;

   m3dSPL_INFO (void);
   m3dSPL_INFO&   operator += (const m3dSPL_INFO &inf);
};

class fioFILE;

class m3dSPL {
public:
   enum {
      FIND_SEG_LEFT  = -1,
      FIND_SEG_RIGHT = -2
   };

public:
   m3dSPL (const m3dSPL &from);
   virtual ~m3dSPL (void);

   static m3dSPL*    Make              (int type, int nKp = 0);

   int               Type              (void) const                     { return type; }
   int               NKp               (void) const                     { return nKp; }
   int               GetValueDim       (void) const                     { return valDim; }
   const apSTATE_8&  GetState          (void) const                     { return state; }
   BOOL              IsIntArg          (void) const                     { return state.Is(M3D_SPL_ST_CHUNKED | M3D_SPL_RAW_ST_NO_ARG); }
   BOOL              IsRawWithArg      (void) const                     { return !state.Is(M3D_SPL_ST_CHUNKED | M3D_SPL_RAW_ST_NO_ARG); }

   void              AllocSpline       (int _nKp);
   void              AllocSplineZeroMem(int _nKp);
   void              RemoveKp          (int kpNmb, int nKp);
   void              InsertKp          (int kpNmb, int nKp);
   void              AddKp             (int numKp);

   float             GetKpArg          (int kpNmb) const;
   void              GetKpValue        (int kpNmb, void *pValue) const;       // value
   void              GetKpData         (int kpNmb, void *pValue) const;       // value + extra
   void              GetKp             (int kpNmb, m3dSPL_KP *pKp) const;     // arg + value + extra

   void              SetKpArg          (int kpNmb, float arg);
   void              SetKpValue        (int kpNmb, const void *pValue);       // value
   void              SetKpData         (int kpNmb, const void *pValue);       // value + extra
   void              SetKp             (int kpNmb, const m3dSPL_KP *pKp);     // arg + value + extra

   virtual void      GetValue          (float arg, void *pValue, void *pValueDif = NULL) const           = 0;
   virtual void      InterpolateKp     (void *kpData1, void *kpData2, float t, void *resKpData) const;
   virtual void      GetValueClosest   (float arg, void *pValue, void *pValueDif = NULL) const           { GetValue(arg, pValue, pValueDif); }  // default behavior
   virtual float     DistPoint         (void *point, int *closSegNmb, float *tMin, void *vClosest) const { ASSERT(FALSE); return 0.f; } 

   void              Optimize          (const m3dSPL_OPTIMIZE &opt);
   BOOL              CheckForConst     (float maxErr = 0.003f);
   void              RemoveRepeatableKeys (float acc);
   BOOL              CheckForIntArg    (void);

   // spline manipulation 
   virtual m3dSPL*   Convert           (int _type, int unused = 0) const                           { ASSERT(FALSE); return NULL; }
   m3dSPL*           Duplicate         (void) const;
   m3dSPL*           Sample            (float start, float end, float step) const;
   void              Swap              (m3dSPL *pSpl);
   m3dSPL&           operator=         (const m3dSPL &from);

   static m3dSPL*    ReadOld           (int type, fioFILE *file);
   static m3dSPL*    Read              (fioFILE *file);
   void              Write             (fioFILE *file);

   int               FindSeg           (float arg) const;
   BOOL              FindSeg           (float arg, m3dSPL_KP *pKp1, m3dSPL_KP *pKp2) const;
   void              GetMinMaxValue    (float start, float end, float step, void *minValue, void *maxValue);

   // raw spline access methods
   int               GetRawKpSize      (void) const      { return (state.Is(M3D_SPL_RAW_ST_NO_ARG) ? dataDim : dataDim + 1) * sizeof(float); }
   const m3dSPL_KP&  GetRaw            (int i) const;
   m3dSPL_KP&        GetRaw            (int i);
   float*            GetRawDataPtr     (int i);
   const float*      GetRawDataPtr     (int i) const;

   int               GetDataSize       (void) const     { return dataSize; }  // for info, dbg, etc
   void              GetInfo           (m3dSPL_INFO &info) const;

   void              ByteReorder       (void);
protected:
   m3dSPL (int type, int kpSize, int valSize);
   
private:
   void              GetKpDataChunk    (int kp, float *val) const;

   void              PackChunks        (int chunkSize, struct CHUNK_WRITE_BUF &buf, const m3dSPL_OPTIMIZE &opt) const;
   void              GetKpDataBaseRange(int startKp, int numKp, float *ofs, float *range, float *maxHalfError = NULL) const;

protected:
   apSTATE_8         state;
   BYTE              type;
   BYTE              valDim;              // number of floats in value (min 1, max kpDim - 1)
   BYTE              dataDim;             // number of data floats in keypoint (value + extra)
   int               nKp;
   
   int               dataSize;
   BYTE              *pData;
};


#define M3D_SPL_DECLARE_PURE_ACCESS(KP)         \
   const KP&  GetRaw  (int i) const  { ASSERT(!state.Is(M3D_SPL_ST_CHUNKED | M3D_SPL_RAW_ST_NO_ARG) && i >= 0 && i < nKp); return *(KP*)(pData + i * GetRawKpSize()); } \
         KP&  GetRaw  (int i)        { ASSERT(!state.Is(M3D_SPL_ST_CHUNKED | M3D_SPL_RAW_ST_NO_ARG) && i >= 0 && i < nKp); return *(KP*)(pData + i * GetRawKpSize()); }

//
// Linear 1D splines
//
struct m3dSPL_KP_LINEAR1D : public  m3dSPL_KP {
   float    value;        // keypoint value
};

class m3dSPL_LINEAR1D : public m3dSPL {
public:
   m3dSPL_LINEAR1D (void)    : m3dSPL(M3D_SPL_LINEAR1D, sizeof(m3dSPL_KP_LINEAR1D), sizeof(float)) {}
   m3dSPL_LINEAR1D (int nKp) : m3dSPL(M3D_SPL_LINEAR1D, sizeof(m3dSPL_KP_LINEAR1D), sizeof(float)) { AllocSpline(nKp); }

   virtual void   GetValue    (float arg, void *pValue, void *pValueDif = NULL) const;
   void           SetKp       (int kpNmb, float arg, float value);
   
   M3D_SPL_DECLARE_PURE_ACCESS(m3dSPL_KP_LINEAR1D)
protected:
   //virtual BOOL   CompareKP   (const m3dSPL_KP *p1, const m3dSPL_KP *p2) const;
};

//
// Linear 2D splines
//
struct m3dSPL_KP_LINEAR2D : public  m3dSPL_KP {
   m2dV    value;        // keypoint value
};

class m3dSPL_LINEAR2D : public m3dSPL {
public:
   m3dSPL_LINEAR2D (void)    : m3dSPL(M3D_SPL_LINEAR2D, sizeof(m3dSPL_KP_LINEAR2D), sizeof(m2dV))  {}
   m3dSPL_LINEAR2D (int nKp) : m3dSPL(M3D_SPL_LINEAR2D, sizeof(m3dSPL_KP_LINEAR2D), sizeof(m2dV))  { AllocSpline(nKp); }

   virtual void   GetValue    (float arg, void *pValue, void *pValueDif = NULL) const;
   void           SetKp       (int kpNmb, float arg, const m2dV *value);
   void           SetKp       (int kpNmb, float arg, const m2dV &value)                            { SetKp(kpNmb, arg, &value); }
   void           SetKp       (int kpNmb, float arg, float valueX, float valueY);

   M3D_SPL_DECLARE_PURE_ACCESS(m3dSPL_KP_LINEAR2D)
protected:
   //virtual BOOL   CompareKP   (const m3dSPL_KP *p1, const m3dSPL_KP *p2) const;
};

//
// Linear 3D splines
//
struct m3dSPL_KP_LINEAR3D : public  m3dSPL_KP {
   m3dV    value;        // keypoint value
};
class m3dSPL_LINEAR3D : public m3dSPL {
public:
   m3dSPL_LINEAR3D (void)    : m3dSPL(M3D_SPL_LINEAR3D, sizeof(m3dSPL_KP_LINEAR3D), sizeof(m3dV))  {}
   m3dSPL_LINEAR3D (int nKp) : m3dSPL(M3D_SPL_LINEAR3D, sizeof(m3dSPL_KP_LINEAR3D), sizeof(m3dV))  { AllocSpline(nKp); }

   virtual void   GetValue          (float arg, void *pValue, void *pValueDif = NULL) const;
   virtual void   GetValueClosest   (float arg, void *pValue, void *pValueDif = NULL) const;
   virtual float  DistPoint         (void *point, int *closSegNmb, float *tMin, void *vClosest) const;
   void           SetKp             (int kpNmb, float arg, const m3dV *value);
   void           SetKp             (int kpNmb, float arg, const m3dV &value)                      { SetKp(kpNmb, arg, &value); }
   void           SetKp             (int kpNmb, float arg, float x, float y, float z);
   void           GetKp             (int kpNmb, float &arg, m3dV &value);

   M3D_SPL_DECLARE_PURE_ACCESS(m3dSPL_KP_LINEAR3D)
protected:
   //virtual BOOL   CompareKP   (const m3dSPL_KP *p1, const m3dSPL_KP *p2) const;
};


//
// Hermit spline
//
struct m3dSPL_KP_HERMIT : public  m3dSPL_KP {
   float    value;      // keypoint value
   float    slopeLeft;  // left  Hermit slope
   float    slopeRight; // right Hermit slope
};

class m3dSPL_HERMIT : public m3dSPL {
public:
   m3dSPL_HERMIT (void)    : m3dSPL(M3D_SPL_HERMIT, sizeof(m3dSPL_KP_HERMIT), sizeof(float))       {}
   m3dSPL_HERMIT (int nKp) : m3dSPL(M3D_SPL_HERMIT, sizeof(m3dSPL_KP_HERMIT), sizeof(float))       { AllocSpline(nKp); }

   virtual void   GetValue          (float arg, void *pValue, void *pValueDif = NULL) const;
   void           SetKp             (int kpNmb, float arg, float value, float slopeLeft, float slopeRight);

   M3D_SPL_DECLARE_PURE_ACCESS(m3dSPL_KP_HERMIT)
};

//
// Lagrange spline
//
struct m3dSPL_KP_LAGRANGE : public  m3dSPL_KP {
   float    value;      // keypoint value
};

class m3dSPL_LAGRANGE : public m3dSPL {
public:
   m3dSPL_LAGRANGE (void)    : m3dSPL(M3D_SPL_LAGRANGE, sizeof(m3dSPL_KP_LAGRANGE), sizeof(float)) {}
   m3dSPL_LAGRANGE (int nKp) : m3dSPL(M3D_SPL_LAGRANGE, sizeof(m3dSPL_KP_LAGRANGE), sizeof(float)) { AllocSpline(nKp); }

   virtual void   GetValue          (float arg, void *pValue, void *pValueDif = NULL) const;
   void           SetKp             (int kpNmb, float arg, float value);

   M3D_SPL_DECLARE_PURE_ACCESS(m3dSPL_KP_LAGRANGE)
};

//
// Bezier 2D spline
//
class _m3dSPL_BEZIER : public m3dSPL {
public:
   _m3dSPL_BEZIER (int _type, int _kpSize, int _valSize) : m3dSPL (_type, _kpSize, _valSize) {};

protected:
   void CalcValue    (float arg, int dim, void* vLeft,  void* vDirLeft, 
                      void* vRight, void* vDirRight, void* pValue, void *pValueDif) const;
};

struct m3dSPL_KP_BEZIER2D : public m3dSPL_KP {
   m2dV     value;      // keypoint value
   m2dV     dirLeft;    // Left  Bezier 2D slope
   m2dV     dirRight;   // Right Bezier 2D slope
};

class m3dSPL_BEZIER2D : public _m3dSPL_BEZIER {
public:
   m3dSPL_BEZIER2D (void)    : _m3dSPL_BEZIER(M3D_SPL_BEZIER2D, sizeof(m3dSPL_KP_BEZIER2D), sizeof(m2dV)) {}
   m3dSPL_BEZIER2D (int nKp) : _m3dSPL_BEZIER(M3D_SPL_BEZIER2D, sizeof(m3dSPL_KP_BEZIER2D), sizeof(m2dV)) { AllocSpline(nKp); }

   virtual void   GetValue       (float arg, void *pValue, void *pValueDif = NULL) const;
   void           SetKp          (int kpNmb, float arg, const m2dV *value, const m2dV *dirLeft, const m2dV *dirRight);
   void           SetKp          (int kpNmb, float arg, const m2dV &value, const m2dV &dirLeft, const m2dV &dirRight) { SetKp(kpNmb, arg, &value, &dirLeft, &dirRight); }

   M3D_SPL_DECLARE_PURE_ACCESS(m3dSPL_KP_BEZIER2D)
};
//
// Bezier 3D spline
//
struct m3dSPL_KP_BEZIER3D : public m3dSPL_KP {
   m3dV     value;      // keypoint value
   m3dV     dirLeft;    // Left  Bezier 2D slope
   m3dV     dirRight;   // Right Bezier 2D slope
};
class m3dSPL_BEZIER3D : public _m3dSPL_BEZIER {
public:
   m3dSPL_BEZIER3D (void)    : _m3dSPL_BEZIER(M3D_SPL_BEZIER3D, sizeof(m3dSPL_KP_BEZIER3D), sizeof(m3dV)) {}
   m3dSPL_BEZIER3D (int nKp) : _m3dSPL_BEZIER(M3D_SPL_BEZIER3D, sizeof(m3dSPL_KP_BEZIER3D), sizeof(m3dV)) { AllocSpline(nKp); }

   virtual void      GetValue    (float arg, void *pValue, void *pValueDif = NULL) const;
   
   void              SetKp       (int kpNmb, float arg, const m3dV *value, const m3dV *dirLeft, const m3dV *dirRight);
   void              SetKp       (int kpNmb, float arg, const m3dV &value, const m3dV &dirLeft, const m3dV &dirRight) { SetKp(kpNmb, arg, &value, &dirLeft, &dirRight); }
   
   //virtual m3dSPL*   Convert     (int type, int subdivision = 0);

   M3D_SPL_DECLARE_PURE_ACCESS(m3dSPL_KP_BEZIER3D)
};

//
// Quaternion spline
//
struct m3dSPL_KP_QUAT : public m3dSPL_KP {
   m3dQUAT  value;       // keypoint value
};
class m3dSPL_QUAT : public m3dSPL {
public:
   m3dSPL_QUAT (void)    : m3dSPL(M3D_SPL_QUAT, sizeof(m3dSPL_KP_QUAT), sizeof(m3dQUAT)) {}
   m3dSPL_QUAT (int nKp) : m3dSPL(M3D_SPL_QUAT, sizeof(m3dSPL_KP_QUAT), sizeof(m3dQUAT)) { AllocSpline(nKp); }

   virtual void      GetValue          (float arg, void *pValue, void *pValueDif = NULL) const;
   virtual void      GetValueClosest   (float arg, void *pValue, void *pValueDif = NULL) const;
   virtual void      InterpolateKp     (void *kpData1, void *kpData2, float t, void *resKpData) const;
   
   void              SetKp             (int kpNmb, float arg, const m3dQUAT *value);
   void              SetKp             (int kpNmb, float arg, const m3dQUAT &value)    { SetKp(kpNmb, arg, &value); }

protected:
   //virtual BOOL   CompareKP   (const m3dSPL_KP *p1, const m3dSPL_KP *p2) const;
};

//
// Color spline
//
struct  m3dSPL_KP_COLOR : public m3dSPL_KP {
   m3dCOLOR  value;       // keypoint value
};

class m3dSPL_COLOR : public m3dSPL {
public:
   m3dSPL_COLOR (void)    : m3dSPL(M3D_SPL_COLOR, sizeof(m3dSPL_KP_COLOR), sizeof(m3dCOLOR)) {}
   m3dSPL_COLOR (int nKp) : m3dSPL(M3D_SPL_COLOR, sizeof(m3dSPL_KP_COLOR), sizeof(m3dCOLOR)) { AllocSpline(nKp); }

   virtual void      GetValue    (float arg, void *pValue, void *pValueDif = NULL) const;
   void              SetKp       (int kpNmb, float arg, const m3dCOLOR *value);
   void              SetKp       (int kpNmb, float arg, const m3dCOLOR &value)               { SetKp(kpNmb, arg, &value); }
   void              SetKp       (int kpNmb, float arg, float r, float g, float b, float a = 255.f);
   void              Scale_0_1   (void);

protected:
   //virtual BOOL      CompareKP   (const m3dSPL_KP *p1, const m3dSPL_KP *p2) const;
};


//
// end of file 'm3d_spl.h'
//
